package com.gitee.wsl.model.process.call

import com.gitee.wsl.model.process.ProcessModel
import com.gitee.wsl.model.process.request.NullBody
import com.gitee.wsl.model.process.request.ProcessRequest
import com.gitee.wsl.model.process.response.ProcessResponse
import com.gitee.wsl.model.process.response.ResponseContainer
import com.gitee.wsl.prop.AttributeKey
import com.gitee.wsl.prop.Attributes
import com.gitee.wsl.reflect.TypeInfo
import com.gitee.wsl.reflect.instanceOf
import com.gitee.wsl.reflect.typeInfo
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext

/**
 * A pair of a [request] and [processResponse] for a specific [HttpClient].
 *
 * @property client the client that executed the call.
 */
open class ProcessClientCall(
     val client: ProcessModel
) : CoroutineScope {

    private val received: AtomicBoolean = atomic(false)

    override val coroutineContext: CoroutineContext get() = processResponse.coroutineContext

    /**
     * Typed [Attributes] associated to this call serving as a lightweight container.
     */
     val attributes: Attributes get() = request.attributes

    /**
     * The [request] sent by the client.
     */
     lateinit var request: ProcessRequest
        protected set

    /**
     * The [processResponse] sent by the server.
     */
     lateinit var processResponse: ProcessResponse
        protected set

    /*@InternalAPI
     constructor(
        client: HttpClient,
        requestData: HttpRequestData,
        responseData: HttpResponseData
    ) : this(client) {
        this.request = DefaultHttpRequest(this, requestData)
        this.response = DefaultHttpResponse(this, responseData)

        if (responseData.body !is ByteReadChannel) {
            attributes.put(CustomResponse, responseData.body)
        }
    }*/

    protected open val allowDoubleReceive: Boolean = false


    protected open suspend fun getResponseContent() = NullBody

    /**
     * Tries to receive the payload of the [processResponse] as a specific expected type provided in [info].
     * Returns [processResponse] if [info] corresponds to [ProcessResponse].
     *
     * @throws NoTransformationFoundException If no transformation is found for the type [info].
     * @throws DoubleReceiveException If already called [body].
     */
     suspend fun bodyNullable(info: TypeInfo): Any? {
         //TODO()
        try {
            if (processResponse.instanceOf(info.type)) return processResponse
//            if (!allowDoubleReceive && !processResponse.isSaved && !received.compareAndSet(false, true)) {
//                throw DoubleReceiveException(this)
//            }

            val responseData = attributes.getOrNull(CustomResponse) ?: getResponseContent()

            val subject = ResponseContainer(info, responseData)
            val result = client.responsePipeline.execute(this, subject).response.takeIf { it != NullBody }

            if (result != null && !result.instanceOf(info.type)) {
                val from = result::class
                val to = info.type
                throw NoTransformationFoundException(processResponse, from, to)
            }

            return result
        } catch (cause: Throwable) {
            processResponse.cancel("Receive failed", cause)
            throw cause
        }
    }

    /**
     * Tries to receive the payload of the [processResponse] as a specific expected type provided in [info].
     * Returns [processResponse] if [info] corresponds to [ProcessResponse].
     *
     * @throws NoTransformationFoundException If no transformation is found for the type [info].
     * @throws DoubleReceiveException If already called [body].
     * @throws NullPointerException If content is `null`.
     */
    suspend fun body(info: TypeInfo): Any = bodyNullable(info)!!

    override fun toString(): String = "HttpClientCall[${request}, ${processResponse}]"

    internal fun setResponse(processResponse: ProcessResponse) {
        this.processResponse = processResponse
    }

    internal fun setRequest(request: ProcessRequest) {
        this.request = request
    }

     companion object {
        private val CustomResponse: AttributeKey<Any> = AttributeKey("CustomResponse")
    }
}

/**
 * Tries to receive the payload of the [response] as a specific type [T].
 *
 * @throws NoTransformationFoundException If no transformation is found for the type [T].
 * @throws DoubleReceiveException If already called [body].
 */
 suspend inline fun <reified T> ProcessClientCall.body(): T = bodyNullable(typeInfo<T>()) as T

/**
 * Tries to receive the payload of the [response] as a specific type [T].
 *
 * @throws NoTransformationFoundException If no transformation is found for the type [T].
 * @throws DoubleReceiveException If already called [body].
 */
 suspend inline fun <reified T> ProcessResponse.body(): T = call.bodyNullable(typeInfo<T>()) as T

/**
 * Tries to receive the payload of the [response] as a specific type [T] described in [typeInfo].
 *
 * @throws NoTransformationFoundException If no transformation is found for the type info [typeInfo].
 * @throws DoubleReceiveException If already called [body].
 */
@Suppress("UNCHECKED_CAST")
 suspend fun <T> ProcessResponse.body(typeInfo: TypeInfo): T = call.bodyNullable(typeInfo) as T

